package org.litespring.beans.factory.xml;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.litespring.beans.BeanDefinition;
import org.litespring.beans.ConstructorArgument;
import org.litespring.beans.PropertyValue;
import org.litespring.beans.factory.BeanDefinitionStoreException;
import org.litespring.beans.factory.config.RuntimeBeanReference;
import org.litespring.beans.factory.config.TypedStringValue;
import org.litespring.beans.factory.support.BeanDefinitionRegistry;
import org.litespring.beans.factory.support.GenericBeanDefinition;
import org.litespring.core.io.Resource;
import org.litespring.util.ClassUtils;
import org.litespring.util.StringUtils;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;

public class XmlBeanDefinitionReader {

    private static final String ID_ATTR = "id";
    private static final String CLASS_ATTR = "class";
    private static final String SCOPE_ATTR = "scope";
    private static final String PROPERTY_ELEMENT = "property";
    private static final String NAME_ATTR = "name";
    private static final String VALUE_ATTR = "value";
    private static final String REF_ATTR = "ref";
    private static final String CONSTRUCTOR_ARG_ELEMENT = "constructor-arg";
    private static final String TYPE_ATTR = "type";

    private BeanDefinitionRegistry beanDefinitionRegistry;

    public XmlBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
        this.beanDefinitionRegistry = beanDefinitionRegistry;
    }

    public void loadBeanDefinition(String configFile) {
        InputStream inputStream = null;
        ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
        inputStream = classLoader.getResourceAsStream(configFile);
        //Dom4J 解析configFile
        loadBeanDefinition(inputStream);
    }

    public void loadBeanDefinition(Resource resource){
        try {
            loadBeanDefinition(resource.getInputStream());
        } catch (FileNotFoundException e) {
            throw new BeanDefinitionStoreException("parsing XML document failed ");
        }
    }

    public void loadBeanDefinition(InputStream inputStream) {
        //Dom4J 解析configFile
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(inputStream);

            Element root = document.getRootElement();
            Iterator<Element> iterator = root.elementIterator();
            while (iterator.hasNext()) {
                Element element = iterator.next();
                String id = element.attributeValue(ID_ATTR);
                String className = element.attributeValue(CLASS_ATTR);
                BeanDefinition beanDefinition = new GenericBeanDefinition(id, className);
                String scope = element.attributeValue(SCOPE_ATTR);
                if ( scope!= null) {
                    beanDefinition.setScope(scope);
                }
                this.parseConstructorArgElements(element, beanDefinition);
                this.parsePropertyElement(element,beanDefinition);

                beanDefinitionRegistry.registryBeanDefinition(id, beanDefinition);
            }
        } catch (DocumentException e) {
            throw new BeanDefinitionStoreException("parsing XML document failed ");
        }
    }

    private void parseConstructorArgElements(Element element, BeanDefinition beanDefinition) {
        Iterator iter = element.elementIterator(CONSTRUCTOR_ARG_ELEMENT);
        while (iter.hasNext()) {
            Element ele = (Element) iter.next();
            parseConstructorArgElement(ele,beanDefinition);
        }
    }

    private void parseConstructorArgElement(Element ele, BeanDefinition beanDefinition) {
        String typeAttr = ele.attributeValue(TYPE_ATTR);
        String nameAttr = ele.attributeValue(NAME_ATTR);
        Object value = parsePropertyValue(ele, beanDefinition, null);
        ConstructorArgument.ValueHolder valueHolder = new ConstructorArgument.ValueHolder(value);
        if (StringUtils.hasLength(typeAttr)) {
            valueHolder.setType(typeAttr);
        }
        if (StringUtils.hasLength(nameAttr)) {
            valueHolder.setName(nameAttr);
        }
        beanDefinition.getConstructorArgument().addArgumentValue(valueHolder);
    }

    private void parsePropertyElement(Element element, BeanDefinition beanDefinition) {
        Iterator iter = element.elementIterator(PROPERTY_ELEMENT);
        while (iter.hasNext()) {
            Element proElem = (Element) iter.next();
            String propertyName = proElem.attributeValue(NAME_ATTR);
            if (!StringUtils.hasLength(propertyName)) {
                return;
            }
            Object val = parsePropertyValue(proElem,beanDefinition,propertyName);
            PropertyValue propertyValue = new PropertyValue(propertyName, val);
            beanDefinition.getPropertyValues().add(propertyValue);
        }
    }

    private Object parsePropertyValue(Element proElem, BeanDefinition beanDefinition, String propertyName) {
        boolean hasRefAttribute = (proElem.attributeValue(REF_ATTR) != null);
        boolean hasValueAttribute = (proElem.attributeValue(VALUE_ATTR) != null);
        if (hasRefAttribute) {
            String proName = proElem.attributeValue(REF_ATTR);
            if (!StringUtils.hasText(proName)) {

            }

            RuntimeBeanReference reference = new RuntimeBeanReference(proName);
            return reference;
        } else if (hasValueAttribute) {
            return new TypedStringValue(proElem.attributeValue(VALUE_ATTR));
        } else {
            throw new RuntimeException();
        }
    }
}
